package com.lolo.json.restlet;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;

@SuppressWarnings("serial")
public class JsonRESTlet extends HttpServlet {
	
	private static final String HANDLER_PREFIX = "handler:";
	private Pattern serviceRegistryPattern = Pattern.compile("/([0-9]+\\.[0-9]+)/([a-zA-Z][a-zA-Z0-9]*)");
	
	private Map<String, JsonServletRequestHandlerWrapper> handlers = new HashMap<String, JsonServletRequestHandlerWrapper>();

	private class JsonRESTRequest {
		
		/* example: /1.1/articles/list 
		 * 			/version/service/action
		 * */
		private Pattern uriPattern = Pattern
				.compile("/([0-9]+\\.[0-9]+)/([a-zA-Z][a-zA-Z0-9]*)/([\\$]?[a-zA-Z][a-zA-Z0-9]*)");

		private String version;
		private String serviceName;
		private String action;

		public JsonRESTRequest(String pathInfo) throws ServletException {
			if (pathInfo == null || pathInfo.equals("")) {
				throw new ServletException("no version, service, action passted pathinfo");
			}
			// regex parse pathInfo
			Matcher matcher = uriPattern.matcher(pathInfo);
			if (matcher.find()) {
				setVersion(matcher.group(1));
				setServiceName(matcher.group(2));
				setAction(matcher.group(3));
				return;
			}
			throw new ServletException("Invalid URI");
		}

		public String getVersion() {
			return version;
		}

		public void setVersion(String version) {
			this.version = version;
		}

		public String getServiceName() {
			return serviceName;
		}

		public void setServiceName(String serviceName) {
			this.serviceName = serviceName;
		}

		public String getAction() {
			return action;
		}

		public void setAction(String action) {
			this.action = action;
		}

	}
	
	public static class JsonServletRequestHandlerWrapper {

		JsonServletRequestHandler handler;
		XStream xstream;
		public JsonServletRequestHandlerWrapper(JsonServletRequestHandler instance, String version, String serviceName) {
			super();
			this.handler = instance;
			this.setVersion(version);
			this.setServiceName(serviceName);
			this.initXStream();
		}
		private void initXStream() {
			XStream xstream = new XStream(new JettisonMappedXmlDriver());  
			xstream.setMode(XStream.NO_REFERENCES);  
			xstream.alias("meta", JsonMetaData.class);  
			xstream.alias("action", JsonMetaData.Action.class); 
			this.xstream = xstream;

		}
		private void setVersion(String version) {
			this.handler.setVersion(version);
		}

		public String getVersion() {
			return this.handler.getVersion();
		}

		private void setServiceName(String serviceName) {
			this.handler.setServiceName(serviceName);
		}

		public String getServiceName() {
			return this.handler.getServiceName();
		}

		public JsonMetaData getMetaData() throws IOException {
			JsonMetaData meta = new JsonMetaData(this.getVersion(), this.getServiceName());
			this.handler.getMetaData(meta);
			return meta;
		}

		public Object executeRequest(String action, HttpServletRequest data)
				throws IOException {
			this.handler.configureJsonMapping(
					action, 
					xstream);
			return this.handler.executeRequest(action, data);
		}
		public String toXML(Object response) {
			return this.xstream.toXML(response);
		}
		
	}

	
	protected JsonServletRequestHandlerWrapper getHandler(JsonRESTRequest jsonRequest) throws ServletException {
		JsonServletRequestHandlerWrapper handler = this.handlers.get("/"+jsonRequest.getVersion()+"/"+jsonRequest.getServiceName());
		if (handler == null) {
			throw new ServletException(jsonRequest + " is not registered");
		} else {
			return handler;
		}
	}
	
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		try {
			try {
				String jsonResponse = executeRequest(request);
				response.setContentType("application/json");
				response.addHeader("Expires", "-1");
				response.addHeader("Pragma", "no-cache");
				response.setContentLength(jsonResponse.length());
				response.getWriter().append(jsonResponse);
				response.setStatus(200);
			} catch (IOException e) {
				response.setStatus(500);
				throw e;
			} catch (ServletException e) {
				response.setStatus(400);
				throw e;
			}
		} finally {
//			response.flushBuffer();
		}
	}

	protected String executeRequest(HttpServletRequest request)
			throws ServletException, IOException {
		JsonRESTRequest jsonRequest = new JsonRESTRequest(request.getPathInfo());
		JsonServletRequestHandlerWrapper handler = this.getHandler(jsonRequest);		
		Object response = null;
		if (jsonRequest.getAction().equals("")) {
			throw new ServletException("action is undefined");
		} else if (jsonRequest.getAction().equals("$meta")) {
			response = handler.getMetaData();
		} else  {
			response = handler.executeRequest(
					jsonRequest.getAction(), 
					request);
		}
		return handler.toXML(response);  
	}

	@SuppressWarnings("rawtypes")
	@Override
	public void init() throws ServletException {
		ServletConfig config = this.getServletConfig();
		Enumeration e = config.getInitParameterNames();
		while (e.hasMoreElements()) {
			String handlerName = (String)e.nextElement();
			if (handlerName.startsWith(HANDLER_PREFIX)) {
				registerHandler(config, handlerName);
			}
		}
	}

	private void registerHandler(ServletConfig config, String handlerName)
			throws ServletException {
		String handlerKey = handlerName.substring(HANDLER_PREFIX.length());

		String className = config.getInitParameter(handlerName);
		if (className != null && !className.equals("")) {
			try {
				JsonServletRequestHandler handler = (JsonServletRequestHandler)(Class.forName(className)).newInstance();
				// regex parse pathInfo
				Matcher matcher = serviceRegistryPattern.matcher(handlerKey);
				if (matcher.find()) {
					String version = matcher.group(1);
					String serviceName = matcher.group(2);
					JsonServletRequestHandlerWrapper wrapper = new JsonServletRequestHandlerWrapper(handler, version, serviceName);
					handlers.put(handlerKey, wrapper);
				} else {
					throw new ServletException("Invalid URI "+handlerKey+" in handler registry");
				}
			} catch (InstantiationException e) {
				throw new ServletException("json.handler.className instance creation exception", e);
			} catch (IllegalAccessException e) {
				throw new ServletException("json.handler.className instance creation exception", e);
			} catch (ClassNotFoundException e) {
				throw new ServletException("json.handler.className class not found", e);
			}
		} else {
			throw new ServletException("json.handler.className is not specified as a servlet parameter");
		}
	}
	

}
